/*
 *  Copyright (C) 2009 Antonio Mata Chamorro
 *  
 *  Este programa es software libre: usted puede redistribuirlo y/o modificarlo
 *  bajo los t�rminos de la Licencia P�blica General GNU publicada
 *  por la Fundaci�n para el Software Libre, ya sea la versi�n 3
 *  de la Licencia, o (a su elecci�n) cualquier versi�n posterior.
 *  
 *  Este programa se distribuye con la esperanza de que sea �til, pero 
 *  SIN GARANT�A ALGUNA; ni siquiera la garant�a impl�cita
 *  MERCANTIL o de APTITUD PARA UN PROP�SITO DETERMINADO. 
 *  Consulte los detalles de la Licencia P�blica General GNU para obtener 
 *  una informaci�n m�s detallada. 
 *  
 *  Deber�a haber recibido una copia de la Licencia P�blica General GNU
 *  junto a este programa. 
 *  En caso contrario, consulte <http://www.gnu.org/licenses/>.
 *   
 */
package org.javahispano.javacup.tacticas_aceptadas.amata.com.amata.javacup.tacticas;

import java.util.LinkedList;

import org.javahispano.javacup.modelo.Comando;
import org.javahispano.javacup.modelo.ComandoGolpearBalon;
import org.javahispano.javacup.modelo.ComandoIrA;
import org.javahispano.javacup.modelo.Constantes;
import org.javahispano.javacup.modelo.JugadorDetalle;
import org.javahispano.javacup.modelo.Posicion;
import org.javahispano.javacup.modelo.SituacionPartido;

/**
 * SoccerPlayer representa un jugador de f�tbol. Cada jugador se puede encontrar
 * en 4 estados: esperando, esperando por el bal�n, corriendo hacia un punto o
 * corriendo hacia el bal�n.
 * <p/>
 * <ul>
 * <li>En el estado "esperando" no se hace nada, solo comprobar si el equipo
 * desea que corramos hacia un punto o hacia el bal�n</li>
 * <li>En el estado "esperando por el bal�n" se envia un mensaje al jugador que
 * tiene el bal�n para que nos la pase y esperamos a que nos llegue la pelota</li>
 * <li>En el estado "corriendo hacia un punto" corremos hacia un punto del
 * terreno de juego, salvo que nos pidan ir por la pelota</li>
 * <li>En el estado "corriendo hacia el bal�n" corremos hacia la pelota para
 * tratar de capturarla</li>
 * </ul>
 * <p/>
 * 
 * @author Antonio Mata Chamorro
 * */
public class SoccerPlayer {
	public enum SoccerPlayerState {
		waiting, waitingForBall, runningToSpot, runningToBall
	};

	private SoccerPlayerState state;
	private SoccerTeam team;

	private float autoPassForce = 0.4f;
	private float passForce = 1f;
	private int stepsWaitingForBall = 2;
	private float stabilizationFactorForStrike = 0.8f;
	private float stabilizationFactorForPass = 1f;

	private JugadorDetalle detalle;
	private Posicion posicion;
	private int number;
	private Spot defensiveHomeSpot;
	private Spot offensiveHomeSpot;
	private SoccerMessage lastMessage;
	private Spot targetSpot;
	private int countWaitingForBall = stepsWaitingForBall;
	private SpotResults results = new SpotResults();
	private Kick kick = new Kick();
	private boolean supportingSpot;
	private boolean lastTouchWasAutoPass = false;
	
	public SoccerPlayer(SoccerTeam team) {
		this.team = team;
		this.state = SoccerPlayerState.waiting;

		// configuracion
		this.autoPassForce = this.team.getMatch().getConfiguration()
				.getAutoPassForce();
		this.passForce = this.team.getMatch().getConfiguration().getPassForce();
		this.stepsWaitingForBall = this.team.getMatch().getConfiguration()
				.getStepsWaitingForBall();
		this.stabilizationFactorForStrike = this.team.getMatch()
				.getConfiguration().getStabilizationFactorForStrike();
		this.stabilizationFactorForPass = this.team.getMatch()
				.getConfiguration().getStabilizationFactorForPass();
	}

	public void setInfo(JugadorDetalle detalle, Posicion posicion, int number) {
		this.detalle = detalle;
		this.posicion = posicion;
		this.number = number;
	}

	/**
	 * A cada jugador se le puede pasar solo un mensaje por cada paso del juego.
	 * Este mensaje puede ser fijado por el SoccerTeam al que el jugador
	 * pertenece o por otro SoccerPlayer. El jugador interpretara el mensaje
	 * como una orden de lo que debe de hacer.
	 * 
	 * @param msg
	 *            mensaje con todos los par�metros de la acci�n a realizar por
	 *            el jugador
	 * */
	public void setMessage(SoccerMessage msg) {
		this.lastMessage = msg;
	}

	/**
	 * Mensaje que el jugador recibe de su SoccerTeam o de otro SoccerPlayer. El
	 * jugador interpretara el mensaje como una orden de lo que debe de hacer.
	 * 
	 * @return mensaje con todos los par�metros de la acci�n a realizar por el
	 *         jugador
	 * */
	public SoccerMessage getMessage() {
		return this.lastMessage;
	}

	public JugadorDetalle getDetalle() {
		return detalle;
	}

	public SoccerTeam getTeam() {
		return this.team;
	}

	/**
	 * Posicion defensiva del jugador
	 * 
	 * @return posicion en el campo del juego donde el jugador debe ubicarse al
	 *         defender
	 * */
	public Spot getDefensiveHomeSpot() {
		return defensiveHomeSpot;
	}

	public void setDefensiveHomeSpot(Spot defensiveHomeSpot) {
		this.defensiveHomeSpot = defensiveHomeSpot;
	}

	/**
	 * Posicion ofensiva del jugador
	 * 
	 * @return posicion en el campo del juego donde el jugador debe ubicarse al
	 *         atacar
	 * */
	public Spot getOffensiveHomeSpot() {
		return offensiveHomeSpot;
	}

	/**
	 * Posicion actual del jugador en el terreno de juego
	 * 
	 * @return posicion del jugador
	 * */
	public Posicion getPosicion() {
		return posicion;
	}

	public void setPosicion(Posicion posicion) {
		this.posicion = posicion;
	}

	public void setOffensiveHomeSpot(Spot offensiveHomeSpot) {
		this.offensiveHomeSpot = offensiveHomeSpot;
	}

	public void setAutoPassForce(float autoPassForce) {
		this.autoPassForce = autoPassForce;
	}

	/**
	 * @see SoccerMatchConfiguration#getAutoPassForce()
	 * */
	public float getAutoPassForce() {
		return autoPassForce;
	}

	/**
	 * @see SoccerMatchConfiguration#getPassForce()
	 * */
	public float getPassForce() {
		return passForce;
	}

	public void setPassForce(float passForce) {
		this.passForce = passForce;
	}

	/**
	 * Numero de dorsal del jugador menos 1.
	 * 
	 * @return (dorsal - 1). Rango de valores: de 0 al 10.
	 * */
	public int getNumber() {
		return this.number;
	}

	/**
	 * Permite saber si el jugador puede golpear la pelota
	 * 
	 * @return true si el jugador tiene a su alcance la pelota
	 * */
	public boolean canKick(SituacionPartido sp) {
		return this.posicion.distancia(this.getTeam().getMatch()
				.getBallPosicion()) <= Constantes.DISTANCIA_CONTROL_BALON;
	}

	/**
	 * Permite saber si el jugador esta en un punto al que tiene ordenador ir
	 * 
	 * @return true si se encuentra en una posicion hacia la que estaba
	 *         corriendo
	 * */
	public boolean inTargetSpot() {
		return posicion.distancia(targetSpot.getPosicion()) < Constantes.DISTANCIA_CONTROL_BALON;
	}

	/**
	 * Si el jugador recibe una peticion de otro jugador para que le pase la
	 * pelota y la posicion de pase que este otro jugador es mejor que la mejor
	 * posicion de pase actual se devuelve true.
	 * 
	 * @return el �ltimo mensaje de PassToMeMessage mejora el mejor punto actual
	 * */
	public boolean betterPassSpot() {
		if (this.team.getAttacker().getMessage() != null
				&& this.team.getAttacker().getMessage() instanceof PassToMeMessage) {
			PassToMeMessage pm = (PassToMeMessage) this.team.getAttacker()
					.getMessage();
			if (pm.getSpot().getValue() > targetSpot.getValue()) {
				return false;
			}
		}

		return true;
	}

	private void strike(Kick kick2, LinkedList<Comando> comandos) {
		comandos.add(new ComandoGolpearBalon(this.number, kick2.getPosicion(),
				kick2.getForce(), kick2.getElevation()));
		lastTouchWasAutoPass = false;
	}

	private void autoPass(Spot spot, LinkedList<Comando> comandos) {
		comandos.add(new ComandoGolpearBalon(this.number, spot.getPosicion(),
				autoPassForce, false));

		lastTouchWasAutoPass = true;
	}

	private void pass(Spot spot, LinkedList<Comando> comandos) {
		double d = this.getPosicion().distancia(spot.getPosicion());

		double f = d
				/ Constantes.getVelocidadRemate(this.getDetalle().getRemate());

		comandos.add(new ComandoGolpearBalon(this.number, spot.getPosicion(), f
				* passForce, false));
		lastTouchWasAutoPass = false;
	}

	/**
	 * Se ejecuta al estar en el estado "esperando". Si se recibe un mensaje
	 * GotoMessage se cambia al estado "corriendo hacia un punto" para
	 * dirigirnos hacia el. Si se recibe un mensaje CatchMessage se cambia al
	 * estado "corriendo hacia la pelota". En otro caso no se hace nada
	 * */
	protected void executeWaitingState(SituacionPartido sp,
			LinkedList<Comando> comandos) {
		if (lastMessage instanceof GotoMessage) {
			this.targetSpot = ((GotoMessage) lastMessage).getSpot();
			this.supportingSpot = ((GotoMessage) lastMessage)
					.isSupportingPosition();
			comandos.add(new ComandoIrA(this.number,
					((GotoMessage) lastMessage).getSpot().getPosicion()));

			this.state = SoccerPlayerState.runningToSpot;
		} else if (lastMessage instanceof CatchMessage) {
			comandos.add(new ComandoIrA(this.number, getTeam().getMatch()
					.getBallPosicion()));

			this.state = SoccerPlayerState.runningToBall;
		}
	}

	/**
	 * 
	 * Se ejecuta al estar en el estado "esperando por el bal�n" se envia
	 * constantemente un mensaje se envia un mensaje PassToMeMessage al jugador
	 * que tiene el bal�n. Si se recibe el bal�n se pasa al estado
	 * "corriendo hacia la pelota". Si pasan stepsWaitingForBall pasos se pasa
	 * al estado "esperando".
	 * 
	 * El valor stepsWaitingForBall esta fijado por la configuracion del
	 * partido.
	 * 
	 * @see SoccerMatchConfiguration#getStepsWaitingForBall()
	 * */
	protected void executeWaitingForBallState(SituacionPartido sp,
			LinkedList<Comando> comandos) {
		this.team.getAttacker().setMessage(
				new PassToMeMessage(this, targetSpot));

		if (canKick(sp)) {
			this.state = SoccerPlayerState.runningToBall;
			countWaitingForBall = stepsWaitingForBall;
			return;
		}

		if (countWaitingForBall-- == 0) {
			this.state = SoccerPlayerState.waiting;
			countWaitingForBall = stepsWaitingForBall;
		}
	}

	/**
	 * Se ejecuta al estar en el estado "corriendo hacia un punto" corremos
	 * hacia la pelota para tratar de capturarla. Si se recibe un mensaje
	 * GotoMessage cambiamos el punto al que ir. Si se recibe un mensaje
	 * CatchMessage o PassToMeMessage se corre hacia la pelota hasta que se
	 * alcanza.
	 * <p/>
	 * Cuando podemos golpear la pelota se calcula si es mejor seguir corriendo
	 * con la pelota, pasar a un compa�ero o tirar a puerta. Esto se calcula
	 * utilizando getTeam().getMatch().getBAPSCalculator(),
	 * getTeam().getMatch().getBSSCalculator() y
	 * getTeam().getMatch().getStrikeCalculator() respectivamente. Los valores
	 * obtenidos se comprueba y se hace lo que mejor puntuaci�n obtenga.
	 * */
	protected void executeRunningToBallState(SituacionPartido sp,
			LinkedList<Comando> comandos) {
		boolean canKick = canKick(sp);

		if (lastMessage instanceof GotoMessage) {
			this.targetSpot = ((GotoMessage) lastMessage).getSpot();
			this.supportingSpot = ((GotoMessage) lastMessage)
					.isSupportingPosition();
			comandos.add(new ComandoIrA(this.number,
					((GotoMessage) lastMessage).getSpot().getPosicion()));

			this.state = SoccerPlayerState.runningToSpot;
		} else if ((lastMessage == null || lastMessage instanceof CatchMessage || lastMessage instanceof PassToMeMessage)
				&& !canKick) {
			comandos.add(new ComandoIrA(this.number, getTeam().getMatch()
					.getBallPosicion()));

			this.state = SoccerPlayerState.runningToBall;
		} else if ((lastMessage == null || lastMessage instanceof CatchMessage || lastMessage instanceof PassToMeMessage)
				&& canKick) {
			// decidimos entre autopase, pase o tiro a puerta

			// mejor autopase
			results.clear();
			results.setNumber(1);
			team.getMatch().getBAPSCalculator().findSpots(this, results);
			Spot bestAutoPassSpot = results.getSpot(0);

			// mejor pase a compa�ero
			results.clear();
			results.setNumber(1);
			((SupportSpotCalculator) team.getMatch().getBSSCalculator())
					.setFindInPlayerSpot(true);
			team.getMatch().getBSSCalculator().findSpots(this, results);
			((SupportSpotCalculator) team.getMatch().getBSSCalculator())
					.setFindInPlayerSpot(false);
			Spot bestPassSpot = results.getNumber() > 0 ? results.getSpot(0)
					: Spot.SPOT_NULL;
			if (!this.lastTouchWasAutoPass)
				bestPassSpot.setValue(bestPassSpot.getValue()
						* this.stabilizationFactorForPass);

			// mejor tiro a puerta
			team.getMatch().getStrikeCalculator().findKick(this, kick);
			if (!this.lastTouchWasAutoPass)
				kick.setValue(kick.getValue()
						* this.stabilizationFactorForStrike);

			// log.info("eval�ate:autopass=" + bestAutoPassSpot.getValue() +
			// ",pass:" + bestPassSpot.getValue() + ", strike=" +
			// kick.getValue());

			if (bestAutoPassSpot.getValue() > bestPassSpot.getValue()) {
				if (bestAutoPassSpot.getValue() > kick.getValue()) {
					autoPass(bestAutoPassSpot, comandos);
					this.state = SoccerPlayerState.runningToBall;
				} else {
					strike(kick, comandos);
					this.state = SoccerPlayerState.waiting;
				}
			} else if (bestPassSpot.getValue() > kick.getValue()) {
				pass(bestPassSpot, comandos);
				this.state = SoccerPlayerState.waiting;
			} else {
				strike(kick, comandos);
				this.state = SoccerPlayerState.waiting;
			}

		}
	}

	/**
	 * Se ejecuta al estar en el estado "corriendo hacia el bal�n" corremos
	 * hacia un punto del terreno de juego. Cuando llegamos al punto si el
	 * mensaje de que ordeno ir a este punto era de soporte
	 * (GotoMessage.isSupportingPosition()) pedimos el pase. Si se recibe el
	 * mensaje GotoMessage se cambia al estado "corriendo hacia un punto". Si se
	 * recibe el mensaje CatchMessage se cambia al estado
	 * "corriendo hacia el bal�n"
	 * */
	protected void executeRunningToSpotState(SituacionPartido sp,
			LinkedList<Comando> comandos) {
		if (lastMessage instanceof GotoMessage && !this.inTargetSpot()) {
			// ignoramos los mensajes goto y seguimos corriendo hacia la
			// posicion
			this.targetSpot = ((GotoMessage) lastMessage).getSpot();
			this.supportingSpot = ((GotoMessage) lastMessage)
					.isSupportingPosition();

			comandos.add(new ComandoIrA(this.number, this.targetSpot
					.getPosicion()));

			this.state = SoccerPlayerState.runningToSpot;
		} else if (lastMessage == null && !this.inTargetSpot()) {
			// si no hay ningun mensajes seguimos corriendo hacia la posicion
			comandos.add(new ComandoIrA(this.number, this.targetSpot
					.getPosicion()));

			this.state = SoccerPlayerState.runningToSpot;
		} else if ((lastMessage == null || lastMessage instanceof GotoMessage)
				&& this.inTargetSpot()) {

			if (this.supportingSpot) {
				// si estamos en una posicion de pase y tenemos la mejor
				// posicion pedimos el pase
				if (this.betterPassSpot()) {
					this.team.getAttacker().setMessage(
							new PassToMeMessage(this, targetSpot));
				}

				this.state = SoccerPlayerState.waitingForBall;
			} else {
				this.state = SoccerPlayerState.waiting;
			}
		} else if (lastMessage instanceof CatchMessage) {
			// comenzamos a correr hacia la pelota
			comandos.add(new ComandoIrA(this.number, getTeam().getMatch()
					.getBallPosicion()));

			this.state = SoccerPlayerState.runningToBall;
		}

	}

	public void update(SituacionPartido sp, LinkedList<Comando> comandos) {
		// comprobar cual seria el nuevo estado
		if (this.state == SoccerPlayerState.waiting) {
			executeWaitingState(sp, comandos);
		} else if (this.state == SoccerPlayerState.waitingForBall) {
			executeWaitingForBallState(sp, comandos);
		} else if (this.state == SoccerPlayerState.runningToBall) {
			executeRunningToBallState(sp, comandos);
		} else if (this.state == SoccerPlayerState.runningToSpot) {
			executeRunningToSpotState(sp, comandos);
		}

		// limpiamos el �ltimo mensaje
		this.lastMessage = null;
	}

	@Override
	public String toString() {
		return "Jugador " + detalle.getNumero() + ", state=" + state;
	}

	/**
	 * @see SoccerMatchConfiguration#getStabilizationFactorForStrike()
	 * */
	public float getStabilizationFactorForStrike() {
		return stabilizationFactorForStrike;
	}

	public void setStabilizationFactorForStrike(
			float stabilizationFactorForStrike) {
		this.stabilizationFactorForStrike = stabilizationFactorForStrike;
	}

	/**
	 * @see SoccerMatchConfiguration#getStabilizationFactorForPass()
	 * */
	public float getStabilizationFactorForPass() {
		return stabilizationFactorForPass;
	}

	public void setStabilizationFactorForPass(float stabilizationFactorForPass) {
		this.stabilizationFactorForPass = stabilizationFactorForPass;
	}

}